commonlibsse_ng\re\c\Calendar/time.rs
1use super::{day::GameDay, month::MonthIndex, year::Year};
2use chrono::{NaiveDate, NaiveDateTime};
3
4/// NewType wrapper for `NaiveDateTime`, representing in-game date and time.
5///
6/// # Example
7/// ```
8/// use commonlibsse_ng::re::Calendar::{GameDateTime, Year, MonthIndex, GameDay, Hour};
9///
10/// let year = Year::new(2025.0);
11/// let month = MonthIndex::new(2.0); // March (0-based)
12/// let day = GameDay::new(28.0);
13/// let hour = Hour::new(15.5); // 15:30
14///
15/// let date_time = GameDateTime::new(year, month, day, hour).unwrap();
16/// assert_eq!(date_time.to_string(), "2025-03-28 15:30:00");
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct GameDateTime(pub NaiveDateTime);
20
21impl Default for GameDateTime {
22 #[inline]
23 fn default() -> Self {
24 Self::DEFAULT
25 }
26}
27
28impl GameDateTime {
29 /// The default in-game date: `77-01-01 00:00:00`.
30 ///
31 /// # Example
32 /// ```
33 /// use commonlibsse_ng::re::Calendar::GameDateTime;
34 /// assert_eq!(GameDateTime::default().to_string(), "0077-01-01 00:00:00");
35 /// ```
36 pub const DEFAULT: Self = Self::from_ymd(77, 1, 1);
37
38 /// Creates a new `GameDateTime` from year, month, day, and hour components.
39 ///
40 /// - The month uses **0-based indexing** internally (`0 = January`, `11 = December`).
41 /// - The day and hour values are clamped to their valid ranges.
42 ///
43 /// Returns `None` if the date or time is invalid.
44 ///
45 /// # Example
46 /// ```
47 /// use commonlibsse_ng::re::Calendar::{GameDateTime, Year, MonthIndex, GameDay, Hour};
48 ///
49 /// let year = Year::new(2025.0);
50 /// let month = MonthIndex::new(2.0); // March
51 /// let day = GameDay::new(15.0);
52 /// let hour = Hour::new(9.75); // 9:45
53 ///
54 /// let date_time = GameDateTime::new(year, month, day, hour).unwrap();
55 /// assert_eq!(date_time.to_string(), "2025-03-15 09:45:00");
56 ///
57 /// // Invalid date returns `None`
58 /// let invalid = GameDateTime::new(year, MonthIndex::new(12.0), day, hour);
59 /// assert!(invalid.is_none());
60 /// ```
61 #[inline]
62 pub fn new(year: Year, month: MonthIndex, day: GameDay, hour: Hour) -> Option<Self> {
63 let month = month.to_clamp_month()?; // 1-based month (1..=12)
64 let day = day.to_clamp_day(month); // Clamped day
65 let (hour, minute) = { (hour.to_hour(), hour.to_minutes()) };
66
67 NaiveDate::from_ymd_opt(year.to_year(), month, day)
68 .and_then(|date| date.and_hms_opt(hour, minute, 0))
69 .map(Self)
70 }
71
72 /// Creates a `NaiveDateTime` from year, month, and day components.
73 ///
74 /// This is a helper function to avoid using deprecated `from_ymd` directly.
75 ///
76 /// # Panics
77 /// - Panics if the provided date or time is invalid or out of range.
78 ///
79 /// # Example
80 /// ```
81 /// use commonlibsse_ng::re::Calendar::GameDateTime;
82 ///
83 /// let date = GameDateTime::from_ymd(2025, 3, 27);
84 ///
85 /// // Panics: invalid date
86 /// // let invalid = from_ymd(2025, 13, 32);
87 /// ```
88 pub const fn from_ymd(year: i32, month: u32, day: u32) -> Self {
89 match NaiveDate::from_ymd_opt(year, month, day) {
90 Some(date) => match date.and_hms_opt(0, 0, 0) {
91 Some(time) => Self(time),
92 None => panic!("Invalid time"),
93 },
94 None => panic!("invalid or out-of-range date"),
95 }
96 }
97}
98
99impl core::fmt::Display for GameDateTime {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{}", self.0)
102 }
103}
104
105/// NewType wrapper for `f32` representing in-game hours(0-based).
106///
107/// Internally stored as a floating-point value:
108/// - `0.0` → `00:00` (midnight)
109/// - `15.5` → `15:30`
110/// - `23.99` → nearly `23:59`
111///
112/// # Example
113/// ```
114/// use commonlibsse_ng::re::Calendar::Hour;
115///
116/// let hour = Hour::new(10.5); // 10:30 AM
117/// assert_eq!(hour.to_hour(), 10);
118/// assert_eq!(hour.to_minutes(), 30);
119///
120/// let max_hour = Hour::new(23.99); // Max valid hour
121/// assert_eq!(max_hour.to_hour(), 23);
122/// assert_eq!(max_hour.to_minutes(), 59);
123/// ```
124#[derive(Debug, Default, Clone, Copy, PartialEq)]
125#[repr(transparent)]
126pub struct Hour(f32);
127
128impl Hour {
129 /// Creates a new `Hour` instance.
130 ///
131 /// # Example
132 /// ```
133 /// use commonlibsse_ng::re::Calendar::Hour;
134 /// let hour = Hour::new(9.75); // 9:45 AM
135 /// assert_eq!(hour.to_hour(), 9);
136 /// assert_eq!(hour.to_minutes(), 45);
137 /// ```
138 #[inline]
139 pub const fn new(value: f32) -> Self {
140 Self(value)
141 }
142
143 /// Returns the hour component (`0..=23`).
144 ///
145 /// # Example
146 /// ```
147 /// use commonlibsse_ng::re::Calendar::Hour;
148 ///
149 /// let hour = Hour::new(14.25); // 14:15
150 /// assert_eq!(hour.to_hour(), 14);
151 /// ```
152 #[inline]
153 pub const fn to_hour(self) -> u32 {
154 self.0 as u32
155 }
156
157 /// Returns the minute component (`0..=59`).
158 ///
159 /// # Example
160 /// ```
161 /// use commonlibsse_ng::re::Calendar::Hour;
162 ///
163 /// let hour = Hour::new(12.5); // 12:30 PM
164 /// assert_eq!(hour.to_minutes(), 30);
165 ///
166 /// let almost_full = Hour::new(23.99); // Nearly 23:59
167 /// assert_eq!(almost_full.to_minutes(), 59);
168 /// ```
169 #[inline]
170 pub fn to_minutes(self) -> u32 {
171 (60.0 * self.0) as u32 % 60
172 }
173}